﻿/*
 * Copyright (c) 2012, University of Southampton All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of University of Southampton nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using Microsoft.SharePoint.WebControls;
using System.Collections.Generic;
using System.Net;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
using System.Text;
using LabTroveWebParts;
using Microsoft.SharePoint.Utilities;
using System.Text.RegularExpressions;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Publishing.WebControls;

namespace LabTroveWebParts.VisualLabTrovePoster
{
    public partial class VisualLabTrovePosterUserControl : UserControl
    {
        public VisualLabTrovePoster ParentWebPart { get; set; }
        /// <summary>
        ///  The short name (sname) of the blog, this is up to 20 characters long.
        /// </summary>
        public string blogShortName { get { return ParentWebPart.blogShortName; } }

        /// <summary>
        /// The {uid} assigned to the user
        /// </summary>
        public string blogUID { get { return ParentWebPart.blogUID; } }

        /// <summary>
        /// The (human readable) username
        /// </summary>
        public string blogUserName { get { return ParentWebPart.blogUserName; } }

        /// <summary>
        /// The base address of the blog, e.g. http://labtrove.example.org
        /// </summary>
        public string blogSite { get { return ParentWebPart.blogSite; } }

        private Dictionary<String, List<String>> _localMetaData;
        public Dictionary<String, List<String>> LocalMetaData
        {
            get
            {
                if (this._localMetaData == null)
                {
                    object localMetaData = ViewState["localMetadata"];
                    if (localMetaData != null && localMetaData is Dictionary<String, List<String>>)
                    {
                        this._localMetaData = localMetaData as Dictionary<String, List<String>>;
                    }
                    else
                    {
                        this._localMetaData = new Dictionary<string, List<string>>();
                        ViewState["localMetadata"] = this._localMetaData;
                    }
                }
                return _localMetaData;
            }
            set
            {
                _localMetaData = value;
                ViewState["localMetadata"] = this._localMetaData;
            }
        }

        private List<data> _attachedData;
        public List<data> AttachedData
        {
            get
            {
                if (this._attachedData == null)
                {
                    object attachedData = ViewState["attachedData"];
                    if (attachedData != null && attachedData is List<data>)
                    {
                        this._attachedData = attachedData as List<data>;
                    }
                    else
                    {
                        this._attachedData = new List<data>();
                        ViewState["attachedData"] = this._attachedData;
                    }
                }
                return _attachedData;
            }
            set
            {
                _attachedData = value;
                ViewState["attachedData"] = this._attachedData;
            }
        }


        private post post;
        private Dictionary<string, string> scriptsDict;
        private const string includeScriptFormat = @"<script language=""{0}"" src=""{1}{2}""></script>";

        public class DataForGridView
        {
            public string TheKey { get; set; }
            public string TheValue { get; set; }
        }

        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);
            this.PreRender+=new EventHandler(LabTrovePoster_PreRender);
            this.DataBinding += new EventHandler(VisualLabTrovePosterUserControl_DataBinding);
            this.dlMetadata.ItemDataBound += new RepeaterItemEventHandler(dlMetadata_ItemDataBound); // += new EventHandler(dlMetadata_DataBinding);
            this.gvAttData.RowDataBound += new GridViewRowEventHandler(gvAttData_RowDataBound);

            // Adds an asset picker for use in the upload of data from SP Libraries.
            this.AddAssetUrlSelectorToPage(pnlAddData, false);

            ClientScriptManager cs = Page.ClientScript;
            // Auto-fill title and extension from file names of Assets picked via Picker.
            string fillAssetJS = "<script type=\"text/javascript\">" +
                                 "function fillAssetFields() { " +
                                 "fname = document.getElementById(\"" + tbAssetFileName.ClientID + "\").value;" +
                                 "fnParts = fname.split(\".\");" +
                                 "document.getElementById(\"" + tbAssetText.ClientID + "\").value = fnParts[0];" +
                                 "document.getElementById(\"" + tbAssetExt.ClientID + "\").value = fnParts[fnParts.length - 1];" +
                                 "}</script>";

            if (!cs.IsClientScriptBlockRegistered("fillAssetFields"))
            {
                cs.RegisterClientScriptBlock(this.GetType(), "fillAssetFields", fillAssetJS);
            }
            tbAssetFileName.Attributes["onchange"] = "fillAssetFields();";

            // Auto-fill filename, title, extension boxes from URL entered.
            string fillUrlJS =  "<script type=\"text/javascript\">" +
                                "function fillUrlFilenames() { " +
                                "url = document.getElementById(\"" + tbExternalUrl.ClientID + "\").value;" +
                                "urlParts = url.split(\"/\");" +
                                "fname = urlParts[urlParts.length - 1];" +
                                "document.getElementById(\"" + tbUrlFileName.ClientID + "\").value = fname; " +
                                "fnParts = fname.split(\".\");" +
                                "document.getElementById(\"" + tbUrlTitle.ClientID + "\").value = fnParts[0];" +
                                "document.getElementById(\"" + tbUrlFileExt.ClientID + "\").value = fnParts[fnParts.length - 1];" +
                                "}</script>";
            
            if (!cs.IsClientScriptBlockRegistered("fillfields"))
            {
                cs.RegisterClientScriptBlock(this.GetType(), "fillfields", fillUrlJS);
            }
            tbExternalUrl.Attributes["onchange"] = "fillUrlFilenames();";
        }

        private void LabTrovePoster_PreRender(object sender, EventArgs e)
        {
            // Set up JavaScript "markItUp" on the main post-editing text box.
            ClientScriptManager cs = Page.ClientScript;

            // For client side JScript, keep name and key in a dictionary.
            scriptsDict = new Dictionary<string, string>()
            {
                {"JS/jquery-1.7.1.min.js", "myjQueryIncludeScript"},
                {"inc/markitup/jquery.markitup.js", "myjQueryMarkItUpIncludeScript"},
                {"inc/markitup/sets/bbcode/set.js", "mySettingsIncludeScript"},
                {"JS/applyMarkItUp.js", "myApply"}
            };

            foreach (KeyValuePair<string, string> pair in scriptsDict)
            {
                if (!cs.IsClientScriptBlockRegistered(pair.Value))
                {
                    string includeScript = String.Format(includeScriptFormat, "javascript", "/_layouts/LabTroveWebParts/" + pair.Key, null);
                    cs.RegisterClientScriptBlock(this.GetType(), pair.Value, includeScript);
                }
            }

            // markItUp needs some style files.
            CssRegistration.Register("/_layouts/LabTroveWebParts" + "/inc/markitup/skins/simple/style.css");
            CssRegistration.Register("/_layouts/LabTroveWebParts" + "/inc/markitup/sets/bbcode/style.css");
            CssRegistration.Register("/_layouts/LabTroveWebParts" + "/inc/style.css");

            // Detect if this is an "edit" request, and if so the load the data.  Customise controls appropriately.
            if (Page.Request.QueryString["postURI"] != null)
            {
                btnAdd.Visible = false;
                pnlEdit.Visible = true;
                if (post == null)
                    post = new post();
                if (ViewState["filled"] == null)
                {
                    fillPost(Page.Request.QueryString["postURI"]);
                    ViewState["filled"] = "true";
                }
            }
            else
            {
                btnAdd.Visible = true;
                pnlEdit.Visible = false;
            }

            // check that we have non-null settings where we need the same.
            this.checkLTSettings();

            // Disply attachecd data, metadata headers only if they are needed
            litH3AttData.Visible = (AttachedData.Count != 0) ? true : false;
            litH3MDTitle.Visible = (LocalMetaData.Count != 0) ? true : false;
        }

        /// <summary>
        /// Check that we have all the required details to access LabTrove, else give a friendly error.
        /// </summary>
        private void checkLTSettings()
        {
            if (blogShortName == null || blogUID == null || blogUserName == null || blogSite == null)
            {
                lblTitle.Visible = false;
                tbTitle.Visible = false;
                lblText.Visible = false;
                tbText.Visible = false;
                lblSection.Visible = false;
                tbSection.Visible = false;
                litH3MDTitle.Visible = false;
                lblKey.Visible = false;
                lblVal.Visible = false;
                tbKey.Visible = false;
                tbVal.Visible = false;
                btnMetadata.Visible = false;
                btnAdd.Visible = false;
                lblResult.Visible = false;
                tbResult.Visible = false;
                lnkBtnBrowse.Visible = false;
                lnkBtnShowAttData.Visible = false;
                lnkBtnShowMD.Visible = false;

                LiteralControl configureMeMsg = new LiteralControl();
                configureMeMsg.Text = "<h1>LabTrove e-Notebook Poster</h1>";
                configureMeMsg.Text += "<p>Please use the Web Part Editor to configure your LabTrove server, user ID, user name and blog name in order to use this web part.</p>";
                Controls.AddAt(0, configureMeMsg);
            }
        }

        /// <summary>
        /// Fire individual databinding events for child controls.
        /// </summary>
        void VisualLabTrovePosterUserControl_DataBinding(object sender, EventArgs e)
        {
            dlMetadata.DataSource = LocalMetaData;
            dlMetadata.DataBind();
            gvAttData.DataSource = AttachedData;
            gvAttData.DataBind();
        }

        /// <summary>
        /// Attaches the metadata we want to use/show.
        /// </summary>
        void dlMetadata_ItemDataBound(object sender, RepeaterItemEventArgs e)
        {
            if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
            {
                Repeater rep = sender as Repeater;
                KeyValuePair<string, List<string>> data = (KeyValuePair<string, List<string>>)e.Item.DataItem;
                GridView gv = e.Item.FindControl("gvValues") as GridView;

                List<DataForGridView> dataForGridView = new List<DataForGridView>();
                foreach (string val in data.Value)
                {
                    dataForGridView.Add(new DataForGridView { TheKey = data.Key, TheValue = val });
                }
                gv.DataSource = dataForGridView;
                gv.DataBind();
            }
        }

        /// <summary>
        /// Simple case, need not do anything.
        /// </summary>
        void gvAttData_RowDataBound(object sender, GridViewRowEventArgs e)
        {
        }

        /// <summary>
        /// Called when delete link is clicked against an attached data item.
        /// </summary>
        protected void gvAttData_RowDeleting(object sender, GridViewDeleteEventArgs e)
        {
            AttachedData.RemoveAt(e.RowIndex);

            gvAttData.DataSource = AttachedData;
            gvAttData.DataBind();
        }

        /// <summary>
        /// Called after deletion, could e.g. check for success.  Currently we do nothing.
        /// </summary>
        protected void gvAttData_RowDeleted(object sender, GridViewDeletedEventArgs e)
        {
        }

        /// <summary>
        /// Called when auto-generated delete link is clicked, to perform the deletion
        /// </summary>
        protected void gv_RowDeleting(object sender, GridViewDeleteEventArgs e)
        {
            GridView gv = (GridView)sender;
            DataKey dataKey = gv.DataKeys[e.RowIndex];
            string theValue = dataKey.Values[1] as string;
            if (theValue != null)
            {
                LocalMetaData[dataKey.Values[0].ToString()].Remove(theValue);
                dlMetadata.DataSource = LocalMetaData;
                dlMetadata.DataBind();
            }
        }

        /// <summary>
        /// Called after deletion, could e.g. check for success.  Currently we do nothing.
        /// </summary>
        protected void gv_RowDeleted(object sender, GridViewDeletedEventArgs e)
        {
            
        }

        /// <summary>
        /// Page lifecycle
        /// </summary>
        protected void Page_Load(object sender, EventArgs e)
        {
        }

        /// <summary>
        /// This routine reads a post from postURI (which is a LabTrove XML representation of a post)
        /// and populates the on-screen controls with the data from the post to enable editing etc.
        /// </summary>
        /// <param name="postURI">A string containing the URI of the XML version of the LabTrove post to read.</param>
        protected void fillPost(string postURI)
        {
            Uri uri = new Uri(postURI);
            WebRequest webReq = WebRequest.Create(uri);
            webReq.Method = WebRequestMethods.Http.Post;
            WebResponse webResp = webReq.GetResponse();

            Stream receiveStream = webResp.GetResponseStream();
            XmlReaderSettings settings = new XmlReaderSettings();
            settings.ConformanceLevel = ConformanceLevel.Auto;
            XmlReader xr = XmlReader.Create(receiveStream, settings);
            xr.MoveToContent();

            String strBBCode = null;
            String strTitle = null;
            String strTime = null;
            String strID = null;
            String strSection = null;
            bool isPost = false;    // Comments and posts both have title, content, html, datestamp elements,
            // so set this only when we know we have a post rather than a comment

            while (xr.Read())
            {
                // Check depth to avoid taking "comment" entries as posts, posts have depth=2, comments have depth=4.
                if (xr.Name == "id" && xr.Depth == 2)
                    strID = xr.ReadElementContentAsString();
                if (xr.Name == "title" && xr.Depth == 2)
                    strTitle = xr.ReadElementContentAsString();
                if (xr.Name == "section" && xr.Depth == 2)
                    strSection = xr.ReadElementContentAsString();
                if (xr.Name == "timestamp" && xr.Depth == 2)
                    strTime = xr.ReadElementContentAsString();
                if (xr.Name == "content" && xr.Depth == 2)
                {
                    isPost = true;
                    strBBCode = xr.ReadInnerXml().Replace(@"<![CDATA[", "").Replace(@"]]>", "").Trim();
                }
                if (xr.Name == "metadata" && xr.Depth == 2)
                {
                    if (LocalMetaData == null)
                        LocalMetaData = new Dictionary<string, List<string>>();

                    xr.Read();

                    while (xr.NodeType != System.Xml.XmlNodeType.EndElement)
                    {
                        string key;
                        string value;
                        string xmlStr = xr.ReadOuterXml();

                        Regex rKey = new Regex(@"<\/.+>");
                        Regex rValue = new Regex(@">.+<");

                        char[] charsToTrim = { '<', '/', '>' };
                        key = rKey.Match(xmlStr).ToString().Trim(charsToTrim);
                        value = rValue.Match(xmlStr).ToString().Trim(charsToTrim);

                        if (LocalMetaData.ContainsKey(key))
                        {
                            foreach (string subVal in value.Split(';'))
                            {
                                LocalMetaData[key].Add(subVal);
                            }
                        }
                        else
                        {
                            LocalMetaData.Add(key, new List<String>());
                            foreach (string subVal in value.Split(';'))
                            {
                                LocalMetaData[key].Add(subVal);
                            }
                        }

                        xr.MoveToContent();
                    }
                }

                if (xr.Name == "attached_data" && xr.Depth == 2)
                {
                    if (AttachedData == null)
                        AttachedData = new List<data>();

                    xr.Read();

                    while (xr.NodeType != System.Xml.XmlNodeType.EndElement)
                    {
                        string myDataString = getDataIDFromString(xr.ReadElementContentAsString());
                        data myData = new data(Convert.ToInt32(myDataString));

                        AttachedData.Add(myData);

                        xr.MoveToContent();
                    }
                }

                // If we have metadata or attached data, set the visible property for the panel.
                if (AttachedData.Count != 0)
                    pnlAddData.Visible = true;
                if (LocalMetaData.Count != 0)
                    pnlMD.Visible = true;
            }

            if (isPost)
            {
                post.title = strTitle;
                tbTitle.Text = post.title;

                XmlDocument xd = new XmlDocument();
                post.content = xd.CreateCDataSection(strBBCode);
                tbText.Text = post.content.InnerText;

                post.section = strSection;
                tbSection.Text = post.section;

                dlMetadata.DataSource = LocalMetaData;
                dlMetadata.DataBind();

                gvAttData.DataSource = AttachedData;
                gvAttData.DataBind();
            }
            else
            {
                displayErrorMsg("Failed to read the post that is selected to be edited.  Please check that the settings for both Web Parts are correct and point to the same Trove, and that the URL has not been altered and try again.");
            }
        }

        /// <summary>
        /// Parses theURI and tries to return the numerical ID of the data referred to.
        /// </summary>
        /// <param name="theURI">URI to parse</param>
        /// <returns>Substring of theURI that contains data ID</returns>
        string getDataIDFromString(string theURI)
        {
            string ID = null;

            string[] parts = Regex.Split(theURI, @"data/");

            if (parts[1] != null)
            {
                string s = parts[1];
                string[] subparts = Regex.Split(s, @".xml");
                ID = subparts[0];
            }

            return ID;
        }

        /// <summary>
        /// Checks that the requisite text boxes are filled in and then invokes the REST API's editpost
        /// action to edit a post in LabTrove.
        /// </summary>
        protected void btnSave_Click(object sender, EventArgs e)
        {
            if (post == null)
                post = new post();

            Page.Validate("postValGroup");
            Page.Validate("editValGroup");

            if (Page.IsValid)
            {

                string errMsg = null;

                if (blogUserName == "" || blogShortName == "" || blogSite == "" || blogUID == "")
                {
                    errMsg = "Please specify your LabTrove user name, blog site,\n";
                    errMsg += "and UID string in the Edit Web Part dialog.";
                }

                if (errMsg != null)
                {
                    // Alert user and abort post.
                    if (!Page.ClientScript.IsClientScriptBlockRegistered("errorMessageAlert-Save"))
                    {
                        string script = "<script language='javascript'>alert('" + errMsg + "')</script>";
                        Page.ClientScript.RegisterClientScriptBlock(GetType(), "errorMessageAlert-Save", script);
                    }
                }
                else
                {
                    XmlDocument xd = new XmlDocument();
                    post.content = xd.CreateCDataSection(Server.HtmlDecode(tbText.Text.ToString()));
                    post.title = tbTitle.Text.ToString();
                    post.section = tbSection.Text.ToString();
                    post.datestamp = DateTime.Now;
                    post.blog_sname = blogShortName;
                    post.author.username = blogUserName;
                    post.metadata = new SerializableStringDictionary<string, List<string>>(LocalMetaData);
                    post.edit_reason = tbEditReason.Text.ToString();
                    post.attached_data = this.AttachedData;
                    post.id = Convert.ToInt32(Page.Request.QueryString["postID"]);

                    MemoryStream postStream = new MemoryStream();
                    // LabTrove needs the message to begin "request=<?xml..."
                    string req = "request=";
                    Encoding encoding = System.Text.Encoding.GetEncoding("utf-8");

                    byte[] ba = encoding.GetBytes(req);
                    postStream.Write(ba, 0, ba.GetLength(0));

                    // The <?xml... part comes from serialsing post.
                    using (XmlTextWriter xmlwriter = new XmlTextWriter(postStream, Encoding.UTF8))
                    {
                        xmlwriter.Formatting = Formatting.None;
                        try
                        {
                            XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
                            ns.Add("", ""); // LabTrove doesn't require a namespace to be used.

                            XmlSerializer xmlSerializer = new XmlSerializer(typeof(post));
                            xmlSerializer.Serialize(xmlwriter, post, ns);
                        }
                        catch (Exception ex)
                        {
                            displayErrorMsg("The post did not serialize properly.  Please be sure that all data entered is valid.  Technical information follows.  " + ex.ToString() + ((ex.InnerException != null)? ex.InnerException.ToString() : ""));
                        }

                        // Use a StringBuilder to produce the REST URL.
                        StringBuilder sb = new StringBuilder("");
                        sb.Append(blogSite);
                        if (sb.ToString()[sb.Length - 1] != '/')
                            sb.Append('/');
                        sb.Append("api/rest/editpost/uid/");
                        sb.Append(blogUID);
                        string url = sb.ToString();

                        WebRequest request = WebRequest.Create(url);

                        request.Method = "POST";
                        request.ContentLength = postStream.Length;
                        request.ContentType = "application/x-www-form-urlencoded";

                        postStream.Seek(0, SeekOrigin.Begin);

                        Stream reqStream = request.GetRequestStream();
                        postStream.WriteTo(reqStream);

                        // Send request and await response.
                        try
                        {
                            WebResponse response = request.GetResponse();
                            Stream receiveStream = response.GetResponseStream();
                            XmlReaderSettings settings = new XmlReaderSettings();
                            settings.ConformanceLevel = ConformanceLevel.Auto;
                            XmlReader xr = XmlReader.Create(receiveStream, settings);

                            postResponse resp = new postResponse();
                            XmlSerializer xs = new XmlSerializer(typeof(postResponse));
                            resp = (postResponse)xs.Deserialize(xr);

                            tbResult.Text = "Status code: " + resp.status_code.ToString();
                            tbResult.Text += "\nSuccess: " + resp.success;
                            tbResult.Text += "\nPost_info: " + resp.post_info;
                            tbResult.Text += "\npost_id: " + resp.post_id.ToString();

                            xr.Close();
                            response.Close();

                            // Clear the form for the next post.
                            LocalMetaData.Clear();
                            dlMetadata.DataSource = LocalMetaData;
                            dlMetadata.DataBind();
                            tbText.Text = "";
                            tbTitle.Text = "";
                            tbSection.Text = "";
                            tbKey.Text = "";
                            tbVal.Text = "";

                            ViewState["filled"] = null;
                        }
                        catch (WebException ex)
                        {
                            string statusDesc = ((((System.Net.HttpWebResponse)(ex.Response)).StatusDescription != null) ? (((System.Net.HttpWebResponse)(ex.Response)).StatusDescription) : "");
                            displayErrorMsg("LabTrove either did not respond, or responded in an unexpected way." +
                                "Please check that this Web Part has been configured with the correct details of" + 
                                "your LabTrove instance, and that LabTrove is running.  Technical information follows.  " + 
                                ex.ToString() + ((ex.InnerException != null) ? ex.InnerException.ToString() : "") + statusDesc);
                        }
                    }

                    Uri uri = Page.Request.Url;
                    if (Page.Request.QueryString["skip"] == null)
                    {
                        Page.Response.Redirect(uri.GetLeftPart(UriPartial.Path));
                    }
                    else
                    {
                        Page.Response.Redirect(uri.GetLeftPart(UriPartial.Path) + "?skip=" + Page.Request.QueryString["skip"]);
                    }
                }
            }
        }

        /// <summary>
        /// Cancels editing a post by clearing the queryString and the text from the posting UI controls.
        /// </summary>
        protected void btnCancel_Click(object sender, EventArgs e)
        {
            post = new post();
            LocalMetaData.Clear();
            Uri uri = Page.Request.Url;
            if (Page.Request.QueryString["skip"] == null)
            {
                Page.Response.Redirect(uri.GetLeftPart(UriPartial.Path));
            }
            else
            {
                Page.Response.Redirect(uri.GetLeftPart(UriPartial.Path) + "?skip=" + Page.Request.QueryString["skip"]);
            }
        }

        /// <summary>
        /// Checks that the requisite details are filled in and then invokes the REST API's addpost action
        /// to add a post to LabTrove.
        /// </summary>
        protected void btnAdd_Click(object sender, EventArgs e)
        {
            if (post == null)
            {
                post = new post();
            }

            string errMsg = null;

            if (blogUserName == "" || blogShortName == "" || blogSite == "" || blogUID == "")
            {
                errMsg = "Please specify your LabTrove user name, blog site,\n";
                errMsg += "and UID string in the Edit Web Part dialog.";
            }

            if (errMsg != null)
            {
                // Alert user and abort post.
                if (!Page.ClientScript.IsClientScriptBlockRegistered("errorMessageAlert-Add"))
                {
                    string script = "<script language='javascript'>alert('" + errMsg + "')</script>";
                    Page.ClientScript.RegisterClientScriptBlock(GetType(), "errorMessageAlert-Add", script);
                }
            }
            else
            {
                XmlDocument xd = new XmlDocument();
                post.content = xd.CreateCDataSection(Server.HtmlDecode(tbText.Text.ToString()));
                post.title = tbTitle.Text.ToString();
                post.section = tbSection.Text.ToString();
                post.datestamp = DateTime.Now;
                post.blog_sname = blogShortName;
                post.author.username = blogUserName;
                post.metadata = new SerializableStringDictionary<string, List<string>>(LocalMetaData);
                post.attached_data = this.AttachedData;

                MemoryStream postStream = new MemoryStream();
                // LabTrove needs the message to begin "request=<?xml..."
                string req = "request=";
                Encoding encoding = System.Text.Encoding.GetEncoding("utf-8");

                byte[] ba = encoding.GetBytes(req);
                postStream.Write(ba, 0, ba.GetLength(0));

                // The <?xml... part comes from serialsing post.
                using (XmlTextWriter xmlwriter = new XmlTextWriter(postStream, Encoding.UTF8))
                {
                    xmlwriter.Formatting = Formatting.None;
                    try
                    {
                        XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
                        ns.Add("", ""); // LabTrove doesn't require a namespace to be used.

                        XmlSerializer xmlSerializer = new XmlSerializer(typeof(post));
                        xmlSerializer.Serialize(xmlwriter, post, ns);
                    }
                    catch (Exception ex)
                    {
                        displayErrorMsg("The post did not serialize properly.  Please be sure that all data entered is valid.  Techanical information follows.  " + ex.ToString() + ((ex.InnerException != null) ? ex.InnerException.ToString() : ""));
                    }

                    // Use a StringBuilder to produce the REST URL.
                    StringBuilder sb = new StringBuilder("");
                    sb.Append(blogSite);
                    if (sb.ToString()[sb.Length - 1] != '/')
                        sb.Append('/');
                    sb.Append("api/rest/addpost/uid/");
                    sb.Append(blogUID);
                    string url = sb.ToString();

                    WebRequest request = WebRequest.Create(url);

                    request.Method = "POST";
                    request.ContentLength = postStream.Length;
                    request.ContentType = "application/x-www-form-urlencoded";

                    postStream.Seek(0, SeekOrigin.Begin);

                    Stream reqStream = request.GetRequestStream();
                    postStream.WriteTo(reqStream);

                    byte[] a = postStream.ToArray();
                    String S = encoding.GetString(a);
                    tbResult.Text += S + "\n\n";

                    // Send request and await response.
                    try
                    {
                        WebResponse response = request.GetResponse();
                        Stream receiveStream = response.GetResponseStream();
                        
                        XmlReaderSettings settings = new XmlReaderSettings();
                        settings.ConformanceLevel = ConformanceLevel.Auto;
                        XmlReader xr = XmlReader.Create(receiveStream, settings);

                        postResponse resp = new postResponse();
                        XmlSerializer xs = new XmlSerializer(typeof(postResponse));
                        resp = (postResponse)xs.Deserialize(xr);

                        tbResult.Text += "\nStatus code: " + resp.status_code.ToString();
                        tbResult.Text += "\nSuccess: " + resp.success;
                        tbResult.Text += "\nPost_info: " + resp.post_info;
                        tbResult.Text += "\npost_id: " + resp.post_id.ToString();

                        xr.Close();
                        response.Close();

                        // Clear the form for the next post.
                        LocalMetaData.Clear();
                        dlMetadata.DataSource = LocalMetaData;
                        dlMetadata.DataBind();
                        tbText.Text = "";
                        tbTitle.Text = "";
                        tbSection.Text = "";
                        tbKey.Text = "";
                        tbVal.Text = "";
                        AttachedData.Clear();
                        gvAttData.DataSource = AttachedData;
                        gvAttData.DataBind();
                    }
                    catch (WebException ex)
                    {
                        string statusDesc = ((((System.Net.HttpWebResponse)(ex.Response)).StatusDescription != null) ? (((System.Net.HttpWebResponse)(ex.Response)).StatusDescription) : "");
                        displayErrorMsg("LabTrove either did not respond, or responded in an unexpected way." +
                            "Please check that this Web Part has been configured with the correct details of" +
                            "your LabTrove instance, and that LabTrove is running.  Technical information follows.  " +
                            ex.ToString() + ((ex.InnerException != null) ? ex.InnerException.ToString() : "") + statusDesc);
                    }
                }
            }
        }

        /// <summary>
        /// Checks that key and value are specified and then adds the key-value pair that are specified in
        /// the text boxes to the metadata for the current post.
        /// </summary>
        protected void btnMetadata_Click(object sender, EventArgs e)
        {

            if (LocalMetaData == null)
            {
                LocalMetaData = new Dictionary<String, List<String>>();
            }

            if (LocalMetaData.ContainsKey(tbKey.Text))
            {
                LocalMetaData[tbKey.Text].Add(tbVal.Text);
            }
            else
            {
                List<string> valList = new List<string>();
                valList.Add(tbVal.Text);
                LocalMetaData.Add(tbKey.Text, valList);
            }

            dlMetadata.DataSource = LocalMetaData;
            dlMetadata.DataBind();
        }

        /// <summary>
        /// Called when the user clicks the show/hide attached data linkbutton, toggles visibility of the Attached Data panel.
        /// </summary>
        protected void lnkBtnShowAttData_Click(object sender, EventArgs e)
        {
            pnlAddData.Visible = !pnlAddData.Visible;
            if (pnlAddData.Visible)
            {
                gvAttData.DataSource = AttachedData;
                gvAttData.DataBind();
            }
        }

        /// <summary>
        /// Called when the user clicks the show/hide metadata linkbutton.
        /// </summary>
        protected void lnkBtnShowMD_Click(object sender, EventArgs e)
        {
            pnlMD.Visible = !pnlMD.Visible;
            if (pnlMD.Visible)
            {
                dlMetadata.DataSource = LocalMetaData;
                dlMetadata.DataBind();
            }
        }

        /// <summary>
        /// Called when the user clicks the "Add attachment by ID" button.
        /// </summary>
        protected void bntAddAtt_Click(object sender, EventArgs e)
        {
            data thisDataItem = new data(Convert.ToInt32(tbDataNum.Text));
            bool alreadyAttached = false;
            foreach (data d in AttachedData)
            {
                if (d.dataNumber == thisDataItem.dataNumber)
                    alreadyAttached = true;
            }

            if(!alreadyAttached)
                AttachedData.Add(thisDataItem);

            tbDataNum.Text = "";

            gvAttData.DataSource = AttachedData;
            gvAttData.DataBind();
        }


        // Edited from: http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.publishing.webcontrols.asseturlselector.aspx#feedback
        private const string ScriptGetAssetUrlValue = "window.location.href";
        /// <summary>
        /// Sets up and adds an AssetUrlSelector to the page for browsing to data to attach.
        /// </summary>
        /// <param name="containerControl">Control to which the asset picker fields will be added</param>
        /// <param name="launchPickerWithCurrentBrowserUrl">bool: sets default launch url</param>
        public void AddAssetUrlSelectorToPage(Control containerControl, bool launchPickerWithCurrentBrowserUrl)
        {
            if (null == containerControl)
            {
                throw new System.ArgumentNullException("containerControl", "The containerControl argument must not be null");
            }
            if (null == containerControl.Page)
            {
                throw new System.ArgumentException("The containerControl argument must be a valid control in a System.Web.UI.Page control tree");
            }

            AssetUrlSelector assetSelector = new AssetUrlSelector();
            // Setting the Page and ID properties is required when the
            // AssetUrlSelector control is not added to the page
            // control tree because
            // the AssetUrlSelector.GetClientLaunchPickerReference()
            // method needs to register script in the page
            assetSelector.Page = containerControl.Page;
            assetSelector.ID = "SampleGenerateAssetUrlSelectorLaunchScript";
            // Uses text box client ID to connect the Asset
            // Picker to the text boxes for the resulting
            //  URL and default text values returned from the
            // Asset Picker dialog box.
            assetSelector.AssetUrlClientID = tbAssetUrl.ClientID;
            assetSelector.AssetTextClientID = tbAssetFileName.ClientID;

            assetSelector.ValidateUrl = true;
            assetSelector.IsUrlRequired = true;
            assetSelector.AllowExternalUrls = false;
            assetSelector.DefaultOpenLocationUrl = "~Site/";
            assetSelector.DefaultToLastUsedLocation = true;
            assetSelector.Visible = false;
            assetSelector.AssetPickerButtonVisible = true;
            assetSelector.AssetUrlTextBoxVisible = true;
            
            string clientLaunchPickerScript;
            if (launchPickerWithCurrentBrowserUrl)
            {
                // Use a client launch script that calculates
                // the current asset URL with custom ECMAScript
                // which in this example always is the current browser location URL
                clientLaunchPickerScript = assetSelector.GetClientLaunchPickerReference(ScriptGetAssetUrlValue);
            }
            else
            {
                // Use the default client launch script that gets the
                // current asset URL value based on the AssetUrlClientID
                clientLaunchPickerScript = assetSelector.GetClientLaunchPickerReference();
            }
            // Add the client launch script as an ondoubleclick handler for the two text boxes
            tbAssetUrl.Attributes["ondblclick"] = clientLaunchPickerScript + "; fillAssetFields(); return false;";
            tbAssetText.Attributes["ondblclick"] = clientLaunchPickerScript + "; fillAssetFields(); return false;";
            lnkBtnBrowse.Attributes["onclick"] = clientLaunchPickerScript + "; fillAssetFields(); return false;";
        }

        /// <summary>
        /// Add from SP library.
        /// </summary>
        protected void btnAddSPLib_Click(object sender, EventArgs e)
        {
            string strUrl = tbAssetUrl.Text;
            string strDataTitle = tbAssetText.Text;
            string strFileName = tbAssetFileName.Text;
            string strFileExt = tbAssetExt.Text;
            
            SPWeb oWebsiteSrc = SPContext.Current.Web;
            SPFile file = oWebsiteSrc.GetFile(strUrl);

            // Make the URL for the REST API.
            string ltAddDataUrl;
            if (blogSite[blogSite.Length - 1] == '/')
            {
                ltAddDataUrl = blogSite + "api/rest/adddata/uid/" + blogUID;
            }
            else
            {
                ltAddDataUrl = blogSite + "/api/rest/adddata/uid/" + blogUID;
            }

            dataset ds = new dataset();
            ds.title = strDataTitle;

            dataitem di = new dataitem();
            di.type = "inline";
            di.ext = strFileExt;
            di.filename = strFileName;
            di.main = "1";
            di.b64encData = Server.UrlEncode(Convert.ToBase64String(file.OpenBinary()));

            ds.data.Add(di);

            MemoryStream postStream = new MemoryStream();
            string req = "request=";
            Encoding encoding = System.Text.Encoding.GetEncoding("utf-8");

            byte[] ba = encoding.GetBytes(req);
            postStream.Write(ba, 0, ba.GetLength(0));

            using (XmlTextWriter xw = new XmlTextWriter(postStream, Encoding.UTF8))
            {
                xw.Formatting = Formatting.None;
                try
                {
                    XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
                    ns.Add("", "");
                    XmlSerializer xs = new XmlSerializer(typeof(dataset));
                    xs.Serialize(xw, ds, ns);
                }
                catch (Exception ex)
                {
                    displayErrorMsg("Data set did not serialize correctly.  Please check that entered data is valid." +
                        "Techanical details follow.  " + ex.ToString() + 
                        (ex.InnerException!=null ? ex.InnerException.ToString():""));
                }

                WebRequest webReq = WebRequest.Create(ltAddDataUrl);

                webReq.Method = WebRequestMethods.Http.Post;
                webReq.ContentLength = postStream.Length;
                webReq.ContentType = "application/x-www-form-urlencoded";
                postStream.Seek(0, SeekOrigin.Begin);

                Stream reqStream = webReq.GetRequestStream();
                postStream.WriteTo(reqStream);

                try
                {
                    WebResponse response = webReq.GetResponse();
                    Stream recvStream = response.GetResponseStream();

                    XmlReaderSettings xrs = new XmlReaderSettings();
                    xrs.ConformanceLevel = ConformanceLevel.Auto;
                    XmlReader xr = XmlReader.Create(recvStream, xrs);

                    dataResponse myDataResponse = new dataResponse();

                    myDataResponse.status_code = 999;
                    myDataResponse.success = "NotYet";
                    myDataResponse.data_id = -1;

                    XmlSerializer xs = new XmlSerializer(typeof(dataResponse));
                    myDataResponse = (dataResponse)xs.Deserialize(xr);

                    tbResult.Text += "Status code: " + myDataResponse.status_code.ToString();
                    tbResult.Text += "\nSuccess: " + myDataResponse.success;
                    tbResult.Text += "\ndata_id: " + myDataResponse.data_id.ToString();

                    xr.Close();
                    response.Close();

                    data thisDataItem = new data(myDataResponse.data_id);
                    AttachedData.Add(thisDataItem);

                    gvAttData.DataSource = AttachedData;
                    gvAttData.DataBind();

                    tbAssetUrl.Text = "";
                    tbAssetText.Text = "";
                    tbAssetFileName.Text = "";
                    tbAssetExt.Text = "";

                }
                catch (WebException ex)
                {
                    string statusDesc = ((((System.Net.HttpWebResponse)(ex.Response)).StatusDescription != null) ? (((System.Net.HttpWebResponse)(ex.Response)).StatusDescription) : "");
                    displayErrorMsg("LabTrove either did not respond, or responded in an unexpected way." +
                        "Please check that this Web Part has been configured with the correct details of" +
                        "your LabTrove instance, and that LabTrove is running.  Technical information follows.  " +
                        ex.ToString() + ((ex.InnerException != null) ? ex.InnerException.ToString() : "") + statusDesc);
                }
            }

        }

        /// <summary>
        /// Uses REST API to submit a URL to LabTrove which is then attached to the post
        /// (NB: LabTrove does not make a copy of the URL, so if it is changed/removed on
        /// the remote site, the post loses the content).
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void btnAddByUrl_Click(object sender, EventArgs e)
        {
            string strUrlToAdd = Server.UrlEncode(tbExternalUrl.Text);
            string strUrlTitle = tbUrlTitle.Text;
            string strUrlFileExt = tbUrlFileExt.Text;
            string strUrlFileName = tbUrlFileName.Text;

            // Make the URL for the REST API.
            string ltAddDataUrl;
            if (blogSite[blogSite.Length - 1] == '/')
            {
                ltAddDataUrl = blogSite + "api/rest/adddata/uid/" + blogUID;
            }
            else
            {
                ltAddDataUrl = blogSite + "/api/rest/adddata/uid/" + blogUID;
            }

            dataset ds = new dataset();
            ds.title = strUrlTitle;

            dataitem di = new dataitem();
            di.type = "url";
            di.ext = strUrlFileExt;
            di.filename = strUrlFileName;
            di.main = "1";
            di.dataUrlString = strUrlToAdd;

            ds.data.Add(di);

            MemoryStream postStream = new MemoryStream();
            string req = "request=";
            Encoding encoding = System.Text.Encoding.GetEncoding("utf-8");

            byte[] ba = encoding.GetBytes(req);
            postStream.Write(ba, 0, ba.GetLength(0));

            using (XmlTextWriter xw = new XmlTextWriter(postStream, Encoding.UTF8))
            {
                xw.Formatting = Formatting.None;
                try
                {
                    XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
                    ns.Add("", "");
                    XmlSerializer xs = new XmlSerializer(typeof(dataset));
                    xs.Serialize(xw, ds, ns);
                }
                catch (Exception ex)
                {
                    displayErrorMsg("Data set did not serialize correctly.  Please check that entered data is valid." +
                        "Techanical details follow.  " + ex.ToString() +
                        (ex.InnerException != null ? ex.InnerException.ToString() : ""));
                }

                WebRequest webReq = WebRequest.Create(ltAddDataUrl);

                webReq.Method = WebRequestMethods.Http.Post;
                webReq.ContentLength = postStream.Length;
                webReq.ContentType = "application/x-www-form-urlencoded";
                postStream.Seek(0, SeekOrigin.Begin);

                Stream reqStream = webReq.GetRequestStream();
                postStream.WriteTo(reqStream);

                try
                {
                    WebResponse response = webReq.GetResponse();
                    Stream recvStream = response.GetResponseStream();

                    XmlReaderSettings xrs = new XmlReaderSettings();
                    xrs.ConformanceLevel = ConformanceLevel.Auto;
                    XmlReader xr = XmlReader.Create(recvStream, xrs);

                    dataResponse myDataResponse = new dataResponse();

                    myDataResponse.status_code = 999;
                    myDataResponse.success = "NotYet";
                    myDataResponse.data_id = -1;

                    XmlSerializer xs = new XmlSerializer(typeof(dataResponse));
                    myDataResponse = (dataResponse)xs.Deserialize(xr);

                    tbResult.Text += "Status code: " + myDataResponse.status_code.ToString();
                    tbResult.Text += "\nSuccess: " + myDataResponse.success;
                    tbResult.Text += "\ndata_id: " + myDataResponse.data_id.ToString();

                    xr.Close();
                    response.Close();

                    data thisDataItem = new data(myDataResponse.data_id);
                    AttachedData.Add(thisDataItem);

                    tbExternalUrl.Text = "";
                    tbUrlTitle.Text = "";
                    tbUrlFileExt.Text = "";
                    tbUrlFileName.Text = "";

                    gvAttData.DataSource = AttachedData;
                    gvAttData.DataBind();

                }
                catch (WebException ex)
                {
                    string statusDesc = ((((System.Net.HttpWebResponse)(ex.Response)).StatusDescription != null) ? (((System.Net.HttpWebResponse)(ex.Response)).StatusDescription) : "");
                    displayErrorMsg("LabTrove either did not respond, or responded in an unexpected way." +
                        "Please check that this Web Part has been configured with the correct details of" +
                        "your LabTrove instance, and that LabTrove is running.  Technical information follows.  " +
                        ex.ToString() + ((ex.InnerException != null) ? ex.InnerException.ToString() : "") + statusDesc);
                }
            }

        }

        private void displayErrorMsg(string ErrMsg)
        {
            this.Controls.Clear();
            this.Controls.Add(new LiteralControl("<h3>An error has occurred</h3>"));
            this.Controls.Add(new LiteralControl("<p style=\"color:red\">" + ErrMsg + "</p>"));
            HyperLink hl = new HyperLink();
            hl.Text = "Back";
            hl.Target = Page.Request.Url.GetLeftPart(UriPartial.Path);
            this.Controls.Add(hl);
        }

    }
}
